﻿
#define SPADAS_DEPRECATED_HIDE
#include "NextCompatibleImage.h"
#include "ffmpegdl.h"

namespace videonext
{
	class CompatibleChannelDataVars : public Vars
	{
	public:
		CompatibleImage owner;
		UInt width;
		UInt height;
		UInt step;
		UInt bbp;
		Byte *data;

		CompatibleChannelDataVars(CompatibleImage& owner, UInt width, UInt height, UInt step, UInt bbp, Byte *data) : owner(owner), width(width), height(height), step(step), bbp(bbp), data(data)
		{}
	};

	class CompatibleImageVars : public Vars
	{
	public:
		CompatibleImageType type;
		Pointer image;
		Bool own;

		CompatibleImageVars(CompatibleImageType type, Pointer image, Bool own) : type(type), image(image), own(own)
		{}

		~CompatibleImageVars()
		{
			if (own && type != CompatibleImageType::None)
			{
				switch (type)
				{
				case CompatibleImageType::ImagePointerColor:
				case CompatibleImageType::ImagePointerGray:
				{
					ImagePointer *imagePtr = (ImagePointer*)image;
					delete imagePtr;
					break;
				}
#if defined(VIDEONEXT_WITH_OPENCV)
				case CompatibleImageType::IplImageGray:
				case CompatibleImageType::IplImageColor:
				{
					IplImage *iplImage = (IplImage*)image;
					cvReleaseImage(&iplImage);
					break;
				}
#endif
				case CompatibleImageType::AVFrameYUV420P:
				case CompatibleImageType::AVFrameNV12:
				case CompatibleImageType::AVFrameYUV422P:
				{
					av_frame_free(&image);
					break;
				}
				case CompatibleImageType::CompatibleChannelGray:
				{
					CompatibleChannelData *channel = (CompatibleChannelData*)image;
					delete channel;
					break;
				}
				default:
					break;
				}
			}
		}
	};
}

using namespace videonext;
using namespace std;

CompatibleChannelData::CompatibleChannelData()
{

}

CompatibleChannelData::CompatibleChannelData(CompatibleImage owner, UInt width, UInt height, UInt step, UInt bbp, Byte *data)
: Object<CompatibleChannelDataVars>(new CompatibleChannelDataVars(owner, width, height, step, bbp, data), TRUE)
{

}

UInt CompatibleChannelData::width()
{
	return vars ? vars->width : 0;
}
UInt CompatibleChannelData::height()
{
	return vars ? vars->height : 0;
}
UInt CompatibleChannelData::step()
{
	return vars ? vars->step : 0;
}
UInt CompatibleChannelData::bbp()
{
	return vars ? vars->bbp : 0;
}
Byte *CompatibleChannelData::data()
{
	return vars ? vars->data : 0;
}

#if defined(VIDEONEXT_WITH_OPENCV)
Bool CompatibleChannelData::iplImage(IplImage& ipl)
{
	if (!vars) return FALSE;
	if (!vars->data) return FALSE;

	bool isColor;
    bool hasAlpha;
	int widthRatio = 1;
	int fixedBBP = vars->bbp;
    switch (vars->bbp)
    {
    case 1:
    	isColor = false;
    	hasAlpha = false;
    	break;
	case 2:
		isColor = false;
		hasAlpha = false;
		widthRatio = 2;
		fixedBBP = 1;
		break;
    case 3:
    	isColor = true;
    	hasAlpha = false;
    	break;
    case 4:
    	isColor = true;
    	hasAlpha = true;
    	break;
    default:
		return FALSE;
    }

	ipl.nChannels = fixedBBP;
    ipl.depth = 8;

	if (isColor)
	{
        ipl.colorModel[0] = 'R';
		ipl.colorModel[1] = 'G';
		ipl.colorModel[2] = 'B';
        ipl.colorModel[3] = '\0';
        ipl.channelSeq[0] = 'B';
		ipl.channelSeq[1] = 'G';
		ipl.channelSeq[2] = 'R';
        if (hasAlpha) ipl.channelSeq[3] = 'A';
        else ipl.channelSeq[3] = '\0';
	}
	else
	{
		ipl.colorModel[0] = 'G';
		ipl.colorModel[1] = 'R';
		ipl.colorModel[2] = 'A';
		ipl.colorModel[3] = 'Y';
		ipl.channelSeq[0] = 'G';
		ipl.channelSeq[1] = 'R';
		ipl.channelSeq[2] = 'A';
		ipl.channelSeq[3] = 'Y';
	}

	ipl.nSize = sizeof(IplImage);
	ipl.ID = 0;
	ipl.alphaChannel = 0;
	ipl.dataOrder = 0;
	ipl.origin = 0;
	ipl.align = 8;
	ipl.width = vars->width * widthRatio;
	ipl.height = vars->height;
	ipl.roi = 0;
	ipl.maskROI = 0;
	ipl.imageId = 0;
	ipl.tileInfo = 0;
	ipl.imageSize = vars->step * vars->height;
	ipl.imageData = (char*)vars->data;
	ipl.widthStep = vars->step;
	ipl.imageDataOrigin = (char*)vars->data;

	for (int i = 0; i < 4; i++)
	{
		ipl.BorderMode[i] = 0;
		ipl.BorderConst[i] = 0;
	}

	return TRUE;
}
#endif

ImagePointer CompatibleChannelData::imagePtr()
{
	if (!vars) return ImagePointer();
	if (!vars->data) return ImagePointer();

	int fixedWidth;
	bool isColor;
	switch (vars->bbp)
	{
	case 1:
		isColor = false;
		fixedWidth = vars->width;
		break;
	case 2:
		isColor = false;
		fixedWidth = vars->width * 2;
		break;
	case 3:
		isColor = true;
		fixedWidth = vars->width;
		break;
	default:
		return ImagePointer();
	}

	ImagePointer ptr(Size2D::wh(fixedWidth, vars->height), isColor);
	UInt copyBytes = math::min(ptr.rowBytes, vars->step);
	for (UInt v = 0; v < vars->height; v++)
	{
		utility::memoryCopy(&vars->data[vars->step * v], &ptr.data[(ptr.rowBytes >> 3) * v], copyBytes);
	}

	return ptr;
}

CompatibleImage::CompatibleImage()
{
}
CompatibleImage::CompatibleImage(CompatibleImageVars *v)
{
	if (v) setVars(v, FALSE);
}
CompatibleImage::CompatibleImage(CompatibleImageType type, Size2D size)
{
	if (size.width < 8 || size.height < 8) return;

	Pointer image;
	switch (type)
	{
	case CompatibleImageType::ImagePointerColor:
		image = new ImagePointer(size, TRUE);
		break;
	case CompatibleImageType::ImagePointerGray:
		image = new ImagePointer(size, FALSE);
		break;
#if defined(VIDEONEXT_WITH_OPENCV)
	case CompatibleImageType::IplImageGray:
		image = cvCreateImage(cvSize(size.width, size.height), IPL_DEPTH_8U, 1);
		break;
	case CompatibleImageType::IplImageColor:
		image = cvCreateImage(cvSize(size.width, size.height), IPL_DEPTH_8U, 3);
		break;
#endif
	case CompatibleImageType::AVFrameYUV420P:
		{
			Pointer frame = av_frame_alloc();
			av_frame_set_format(frame, AVPixelFormat::AV_PIX_FMT_YUV420P);
			av_frame_set_width(frame, size.width);
			av_frame_set_height(frame, size.height);
			av_frame_get_buffer(frame, getFFMPEGLibraryVersion() >= 3 ? 0 : 8);
			image = frame;
		}
		break;
	case CompatibleImageType::AVFrameNV12:
		{
			Pointer frame = av_frame_alloc();
			av_frame_set_format(frame, AVPixelFormat::AV_PIX_FMT_NV12);
			av_frame_set_width(frame, size.width);
			av_frame_set_height(frame, size.height);
			av_frame_get_buffer(frame, getFFMPEGLibraryVersion() >= 3 ? 0 : 8);
			image = frame;
		}
		break;
	case CompatibleImageType::AVFrameYUV422P:
		{
			Pointer frame = av_frame_alloc();
			av_frame_set_format(frame, AVPixelFormat::AV_PIX_FMT_YUV422P);
			av_frame_set_width(frame, size.width);
			av_frame_set_height(frame, size.height);
			av_frame_get_buffer(frame, getFFMPEGLibraryVersion() >= 3 ? 0 : 8);
			image = frame;
		}
		break;
	default:
		return;
	}

	setVars(new CompatibleImageVars(type, image, TRUE), TRUE);
}
CompatibleImage::CompatibleImage(ImagePointer imagePtr)
{
	if (imagePtr.width < 8 || imagePtr.height < 8 || imagePtr.rowBytes < 8) return;
	if (imagePtr.data.size() != imagePtr.rowBytes * imagePtr.height / 8) return;

	CompatibleImageType type = imagePtr.isColor ? CompatibleImageType::ImagePointerColor : CompatibleImageType::ImagePointerGray;
	Pointer image = new ImagePointer(imagePtr);
	setVars(new CompatibleImageVars(type, image, TRUE), TRUE);
}
#if defined(VIDEONEXT_WITH_OPENCV)
CompatibleImage::CompatibleImage(IplImage *iplImage, Bool own)
{
	if (!iplImage) return;
	if (iplImage->depth != 8 || (iplImage->nChannels != 1 && iplImage->nChannels != 3))
	{
		if (own) cvReleaseImage(&iplImage);
		return;
	}

	CompatibleImageType type = iplImage->nChannels == 1 ? CompatibleImageType::IplImageGray : CompatibleImageType::IplImageColor;
	Pointer image = iplImage;
	setVars(new CompatibleImageVars(type, image, own), TRUE);
}
#endif
CompatibleImage::CompatibleImage(Pointer avFrame, Bool own)
{
	if (!avFrame) return;

	auto format = av_frame_get_format(avFrame);
	CompatibleImageType type;
	switch (format)
	{
	case AVPixelFormat::AV_PIX_FMT_YUV420P:
	case AVPixelFormat::AV_PIX_FMT_YUVJ420P:
		type = CompatibleImageType::AVFrameYUV420P;
		break;
	case AVPixelFormat::AV_PIX_FMT_NV12:
		type = CompatibleImageType::AVFrameNV12;
		break;
	case AVPixelFormat::AV_PIX_FMT_YUV422P:
	case AVPixelFormat::AV_PIX_FMT_YUVJ422P:
		type = CompatibleImageType::AVFrameYUV422P;
		break;
	default:
		if (own) av_frame_free(&avFrame);
		return;
	}

	setVars(new CompatibleImageVars(type, avFrame, own), TRUE);
}
CompatibleImage::CompatibleImage(CompatibleChannelData channel)
{
	if (channel.width() < 8 || channel.height() < 8 || channel.step() < 8 || !channel.data()) return;
	if (channel.bbp() != 1) return;

	setVars(new CompatibleImageVars(CompatibleImageType::CompatibleChannelGray, new CompatibleChannelData(channel), TRUE), TRUE);
}

CompatibleImageType CompatibleImage::getType()
{
	return vars ? vars->type : CompatibleImageType::None;
}
Pointer CompatibleImage::getImage()
{
	return vars ? vars->image : 0;
}

UInt CompatibleImage::width()
{
	if (!vars) return 0;

	switch (vars->type)
	{
	case CompatibleImageType::ImagePointerColor:
	case CompatibleImageType::ImagePointerGray:
		return ((ImagePointer*)vars->image)->width;
#if defined(VIDEONEXT_WITH_OPENCV)
	case CompatibleImageType::IplImageGray:
	case CompatibleImageType::IplImageColor:
		return ((IplImage*)vars->image)->width;
#endif
	case CompatibleImageType::AVFrameYUV420P:
	case CompatibleImageType::AVFrameNV12:
	case CompatibleImageType::AVFrameYUV422P:
		return av_frame_get_width(vars->image);
	case CompatibleImageType::CompatibleChannelGray:
		return ((CompatibleChannelData*)vars->image)->width();
	default:
		return 0;
	}
}
UInt CompatibleImage::height()
{
	if (!vars) return 0;

	switch (vars->type)
	{
	case CompatibleImageType::ImagePointerColor:
	case CompatibleImageType::ImagePointerGray:
		return ((ImagePointer*)vars->image)->height;
#if defined(VIDEONEXT_WITH_OPENCV)
	case CompatibleImageType::IplImageGray:
	case CompatibleImageType::IplImageColor:
		return ((IplImage*)vars->image)->height;
#endif
	case CompatibleImageType::AVFrameYUV420P:
	case CompatibleImageType::AVFrameNV12:
	case CompatibleImageType::AVFrameYUV422P:
		return av_frame_get_height(vars->image);
	case CompatibleImageType::CompatibleChannelGray:
		return ((CompatibleChannelData*)vars->image)->height();
	default:
		return 0;
	}
}
Size2D CompatibleImage::size()
{
	return Size2D::wh(width(), height());
}

Array<CompatibleChannelData> CompatibleImage::channels()
{
	Array<CompatibleChannelData> output;
	if (!vars) return output;

	if (vars->type == CompatibleImageType::ImagePointerColor || vars->type == CompatibleImageType::ImagePointerGray)
	{
		ImagePointer *imagePtr = (ImagePointer*)vars->image;

		output = Array<CompatibleChannelData>(1);
		output[0] = CompatibleChannelData(*this, imagePtr->width, imagePtr->height, imagePtr->rowBytes, vars->type == CompatibleImageType::ImagePointerGray ? 1 : 3, (Byte*)imagePtr->data.data());
	}
#if defined(VIDEONEXT_WITH_OPENCV)
	else if (vars->type == CompatibleImageType::IplImageGray || vars->type == CompatibleImageType::IplImageColor)
	{
		IplImage *iplImage = (IplImage*)vars->image;

		output = Array<CompatibleChannelData>(1);
		output[0] = CompatibleChannelData(*this, iplImage->width, iplImage->height, iplImage->widthStep, vars->type == CompatibleImageType::IplImageGray ? 1 : 3, (Byte*)iplImage->imageData);
	}
#endif
	else if (vars->type == CompatibleImageType::AVFrameYUV420P)
	{
		Pointer frame = vars->image;

		output = Array<CompatibleChannelData>(3);
		output[0] = CompatibleChannelData(*this, av_frame_get_width(frame), av_frame_get_height(frame), av_frame_get_linesize(frame)[0], 1, av_frame_get_data(frame)[0]);
		output[1] = CompatibleChannelData(*this, av_frame_get_width(frame) / 2, av_frame_get_height(frame) / 2, av_frame_get_linesize(frame)[1], 1, av_frame_get_data(frame)[1]);
		output[2] = CompatibleChannelData(*this, av_frame_get_width(frame) / 2, av_frame_get_height(frame) / 2, av_frame_get_linesize(frame)[2], 1, av_frame_get_data(frame)[2]);
	}
	else if (vars->type == CompatibleImageType::AVFrameNV12)
	{
		Pointer frame = vars->image;

		output = Array<CompatibleChannelData>(2);
		output[0] = CompatibleChannelData(*this, av_frame_get_width(frame), av_frame_get_height(frame), av_frame_get_linesize(frame)[0], 1, av_frame_get_data(frame)[0]);
		output[1] = CompatibleChannelData(*this, av_frame_get_width(frame) / 2, av_frame_get_height(frame) / 2, av_frame_get_linesize(frame)[1], 2, av_frame_get_data(frame)[1]);
	}
	else if (vars->type == CompatibleImageType::AVFrameYUV422P)
	{
		Pointer frame = vars->image;

		output = Array<CompatibleChannelData>(3);
		output[0] = CompatibleChannelData(*this, av_frame_get_width(frame), av_frame_get_height(frame), av_frame_get_linesize(frame)[0], 1, av_frame_get_data(frame)[0]);
		output[1] = CompatibleChannelData(*this, av_frame_get_width(frame) / 2, av_frame_get_height(frame), av_frame_get_linesize(frame)[1], 1, av_frame_get_data(frame)[1]);
		output[2] = CompatibleChannelData(*this, av_frame_get_width(frame) / 2, av_frame_get_height(frame), av_frame_get_linesize(frame)[2], 1, av_frame_get_data(frame)[2]);
	}
	else if (vars->type == CompatibleImageType::CompatibleChannelGray)
	{
		CompatibleChannelData *channel = (CompatibleChannelData*)vars->image;

		output = Array<CompatibleChannelData>(1);
		output[0] = CompatibleChannelData(*this, channel->width(), channel->height(), channel->step(), 1, channel->data());
	}

	return output;
}
